package com.clg.protobuf.util;

import com.clg.protobuf.dto.RequestDTO;
import com.clg.protobuf.dto.ResponseDTO;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.Parser;
import com.googlecode.protobuf.format.JsonFormat;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.assertj.core.util.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * http请求发送工具类
 * 发送 GET 和 POST 请求
 * 支持 JSON 和 Protocol Buffer 数据格式
 * Created by chenlong on 2017/6/1.
 */
@Component
public class HttpClientUtil {

	private final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
	private final String log_prefix = "==================== HttpClientUtil execute ";

	public ResponseDTO executeHttpRequest(RequestDTO requestDTO, Class clazz)
			throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException {
		logger.info(log_prefix + " executeHttpRequest start =>");
		long startTime = System.currentTimeMillis();
		ResponseDTO responseDTO;
		// GET请求
		if (ConstantUtil.REQUEST_METHOD_TYPE_GET.equalsIgnoreCase(requestDTO.getMethodType())) {
			logger.info(log_prefix + " Request method is GET");
			responseDTO = this.get(requestDTO, clazz);
		} else {
			logger.info(log_prefix + " Request method is POST");
			responseDTO = this.post(requestDTO, clazz);
		}
		long endTime = System.currentTimeMillis();
		logger.info(log_prefix + " getProtobuf end => 共执行时间：" + (endTime - startTime) + " ms");
		return responseDTO;
	}

	/**
	 * 发送GET请求
	 * @param requestDTO
	 * @param clazz
	 * @return
	 * @throws IOException
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public ResponseDTO get(RequestDTO requestDTO, Class<?> clazz)
			throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		ResponseDTO responseDTO = new ResponseDTO();
		CloseableHttpClient httpClient = HttpClients.createDefault();
		// 设置请求超市时间
		RequestConfig requestConfig = RequestConfig.custom()
				.setConnectTimeout(5000).setConnectionRequestTimeout(1000)
				.setSocketTimeout(5000).build();
		// 接口地址
		String interfaceUrl = requestDTO.getInterfaceUrl();
		// 构建请求参数
		String requestParam = this.getRequestParam(requestDTO.getParamData());
		if (!StringUtils.isEmpty(requestParam)) {
			if (interfaceUrl.indexOf("?") == -1) {
				interfaceUrl += "?" + requestParam;
			} else {
				interfaceUrl += "&" + requestParam;
			}
		}
		HttpGet httpGet = new HttpGet(interfaceUrl);
		httpGet.setConfig(requestConfig);
		// 请求数据类型为：application/x-protobuf
		if (ConstantUtil.MEDIA_TYPE_PROTOCOL_BUFFER.equalsIgnoreCase(requestDTO.getProtocolType())) {
			httpGet.addHeader(ConstantUtil.CONTENT_TYPE, ConstantUtil.MEDIA_TYPE_PROTOCOL_BUFFER_URF_8);
			httpGet.addHeader(ConstantUtil.ACCEPT, ConstantUtil.MEDIA_TYPE_PROTOCOL_BUFFER_URF_8);
		} else {
			// 请求数据类型为：application/json
			httpGet.addHeader(ConstantUtil.CONTENT_TYPE, ConstantUtil.MEDIA_TYPE_JSON_URF_8);
			httpGet.addHeader(ConstantUtil.ACCEPT, ConstantUtil.MEDIA_TYPE_JSON_URF_8);
		}
		// 获取请求头信息
		responseDTO.setRequestHeader(this.requestHeaderGet(httpGet));
		HttpResponse httpResponse = httpClient.execute(httpGet);
		// 获取响应头信息
		String responseHeader = this.responseHeader(httpResponse);
		responseDTO.setResponseHeader(responseHeader);
		// 解析响应数据
		String responseBody = this.getResponseData(httpResponse, clazz);
		responseDTO.setResponseBody(responseBody);
		return responseDTO;
	}

	/**
	 * 发送POST请求
	 * @param requestDTO
	 * @param clazz
	 * @return
	 */
	public ResponseDTO post(RequestDTO requestDTO, Class<?> clazz)
			throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		ResponseDTO responseDTO = new ResponseDTO();
		CloseableHttpClient httpClient = HttpClients.createDefault();
		// 设置请求超时时间
		RequestConfig requestConfig = RequestConfig.custom()
				.setConnectTimeout(5000).setConnectionRequestTimeout(1000)
				.setSocketTimeout(5000).build();
		HttpPost httpPost = new HttpPost(requestDTO.getInterfaceUrl());
		httpPost.setConfig(requestConfig);
		StringBuffer requestBody = new StringBuffer();
		// 请求数据类型为：application/x-protobuf
		if (ConstantUtil.MEDIA_TYPE_PROTOCOL_BUFFER.equalsIgnoreCase(requestDTO.getProtocolType())) {
			httpPost.addHeader(ConstantUtil.CONTENT_TYPE, ConstantUtil.MEDIA_TYPE_PROTOCOL_BUFFER_URF_8);
			httpPost.addHeader(ConstantUtil.ACCEPT, ConstantUtil.MEDIA_TYPE_PROTOCOL_BUFFER_URF_8);
			byte[] bytes = this.jsonToProtobufByteArray(requestDTO.getParamData(), clazz);
			requestBody.append("Request Param Protocol Buffer Data: ");
			for (byte b : bytes) {
				requestBody.append(b);
			}
			requestBody.append("<br /><br />");
			requestBody.append("Request Param  JSON Data : ").append(requestDTO.getParamData()).append("<br />");
			httpPost.setEntity(new ByteArrayEntity(bytes));
		} else {
			// 请求数据类型为：application/json
			httpPost.addHeader(ConstantUtil.CONTENT_TYPE, ConstantUtil.MEDIA_TYPE_JSON_URF_8);
			httpPost.addHeader(ConstantUtil.ACCEPT, ConstantUtil.MEDIA_TYPE_JSON_URF_8);
			httpPost.setEntity(new ByteArrayEntity(requestDTO.getParamData().getBytes()));
			requestBody.append("Request Param  JSON Data : ").append(requestDTO.getParamData()).append("<br />");
		}
		// 获取请求头信息
		responseDTO.setRequestHeader(this.requestHeaderPost(httpPost));
		HttpResponse httpResponse = httpClient.execute(httpPost);
		// 设置请求参数信息
		responseDTO.setRequestBody(requestBody.toString());
		// 获取响应头信息
		String responseHeader = this.responseHeader(httpResponse);
		responseDTO.setResponseHeader(responseHeader);
		// 解析响应数据
		String responseBody = this.getResponseData(httpResponse, clazz);
		responseDTO.setResponseBody(responseBody);
		return responseDTO;
	}

	/**
	 * 获取GET请求头信息
	 * @param httpGet
	 * @return
	 */
	private String requestHeaderGet(HttpGet httpGet) {
		Header[] requestHeaders = httpGet.getAllHeaders();
		return this.getHeaderInfo(requestHeaders);
	}

	/**
	 * 获取POST请求头信息
	 * @param httpPost
	 * @return
	 */
	private String requestHeaderPost(HttpPost httpPost) {
		Header[] responseHeaders = httpPost.getAllHeaders();
		return this.getHeaderInfo(responseHeaders);
	}

	/**
	 * 获取响应头信息
	 * @param httpResponse
	 * @return
	 */
	private String responseHeader(HttpResponse httpResponse) {
		Header[] responseHeader = httpResponse.getAllHeaders();
		return this.getHeaderInfo(responseHeader);
	}

	/**
	 * 获取请求头信息
	 * @param headers
	 * @return
	 */
	private String getHeaderInfo(Header[] headers) {
		StringBuffer headerInfo = new StringBuffer();
		for (Header header : headers) {
			headerInfo.append(header.getName()).append(":").append(header.getValue()).append("<br />");
		}
		return headerInfo.toString();
	}

	/**
	 * 将IO流数据转换成ProtocolBuffer的java对象
	 * @param protocolBufferStream  protocolBuffer流数据
	 * @param tClass 转换目标类型
	 * @return ProtocolBuffer的java对象
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 * @throws InvalidProtocolBufferException
	 */
	public Object convertIOStreamToProtoBufObjcet(InputStream protocolBufferStream, Class<?> tClass)
			throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InvalidProtocolBufferException {
		Parser<Message> parser;
		Method method = tClass.getDeclaredMethod("parser");
		parser = (Parser<Message>) method.invoke(null);
		Object object = parser.parseFrom(protocolBufferStream);
		return object;
	}

	/**
	 * 将 Protocol Buffer 转换成 JSON
	 * @param message
	 * @return
	 * @throws IOException
	 */
	public String convertProtobufToJson(Message message) throws IOException {
		JsonFormat jsonFormat = new JsonFormat();
		return jsonFormat.printToString(message);
	}

	/**
	 * 将Json 转换为Map<String, String>
	 * @param jsonString
	 * @return
	 */
	private Map<String, String> jsonToMap(String jsonString) {
		Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
		Type type = new TypeToken<Map<String, String>>() {}.getType();
		Map<String, String> map = gson.fromJson(jsonString, type);
		return map;
	}

	/**
	 * 构建请求参数
	 * @param requestParam
	 * @return
	 */
	private String getRequestParam(String requestParam) throws IOException {
		if (StringUtils.isEmpty(requestParam)) {
			return null;
		}
		Map<String, String> paramMap = this.jsonToMap(requestParam);
		List<NameValuePair> params = Lists.newArrayList();
		for (Map.Entry<String, String> entry : paramMap.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			logger.info("====================请求参数：" + key + " 的值为：" + value);
			params.add(new BasicNameValuePair(key, value));
		}
		String paramData = EntityUtils.toString(new UrlEncodedFormEntity(params, Consts.UTF_8));
		return paramData;
	}

	/**
	 * 将JSON转换为ProtocolBuffer的byte数组
	 * @param jsonString json字符串
	 * @param tClass 转换目标类型
	 * @return
	 */
	private byte[] jsonToProtobufByteArray(String jsonString, Class<?> tClass)
			throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
		Method method = tClass.getDeclaredMethod("newBuilder");
		Message.Builder builder = (Message.Builder) method.invoke(null);
		JsonFormat jsonFormat = new JsonFormat();
		jsonFormat.merge(new ByteArrayInputStream(jsonString.getBytes()), builder);
		return builder.build().toByteArray();
	}

	/**
	 * 获取返回数据
	 * @param httpResponse http 请求响应数据
	 * @param clazz
	 * @return responseData 响应数据
	 * @throws IOException
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private String getResponseData(HttpResponse httpResponse, Class<?> clazz)
			throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		StringBuffer responseBody = new StringBuffer();
		// 返回数据类型
		String responseContentType = httpResponse.getEntity().getContentType().toString();
		// 返回数据类型为：application/x-protobuf1
		if (responseContentType.indexOf(ConstantUtil.MEDIA_TYPE_PROTOCOL_BUFFER) != -1) {
			InputStream responseStream = httpResponse.getEntity().getContent();
			// 将 Protocol Buffer 转换成 JSON
			Object protoBufDTO = this.convertIOStreamToProtoBufObjcet(responseStream, clazz);
			Method toByteArrayMethod = protoBufDTO.getClass().getMethod("toByteArray", null);
			byte[] byteData = (byte[]) toByteArrayMethod.invoke(protoBufDTO,null);
			responseBody.append("Response Protocol Buffer Data: ");
			if (byteData.length > 0) {
				for (byte b : byteData) {
					responseBody.append(b);
				}
			}
			responseBody.append("<br /><br /><br />");
			// JSON 格式数据
			Message message = (Message) protoBufDTO;
			String jsonData = this.convertProtobufToJson(message);
			responseBody.append("Response JSON Data : ").append(jsonData).append("<br/>");
		} else {
			// 返回数据类型为：application/json
			String responseContent = EntityUtils.toString(httpResponse.getEntity());
			responseBody.append("Response JSON Data : ").append(responseContent).append("<br/>");
		}
		return responseBody.toString();
	}
}

